home *** CD-ROM | disk | FTP | other *** search
/ Technotools / Technotools (Chestnut CD-ROM)(1993).ISO / lang_c / cug167 / bu.c next >
Text File  |  1985-08-21  |  30KB  |  984 lines

  1. /* BU.C - A File Backup Utility for CP/M-80 & CP/M-86
  2.  *
  3.  * Copyright:    Ian Ashdown
  4.  *        byHeart Software
  5.  *        2 - 2016 West First Avenue
  6.  *        Vancouver, B.C. V6J 1G8
  7.  *        Canada
  8.  *
  9.  * Acknowledgment: DeSmet C code and suggestions for program
  10.  *            improvement courtesy of Dr. Dobb's Journal
  11.  *           Contributing Editor Anthony Skjellum
  12.  *            
  13.  * Version:    1.2    Written for Aztec CII v1.06b (CP/M-80)
  14.  *            and DeSmet C88 v2.2 (CP/M-86)
  15.  *
  16.  * Date:    December 31st, 1983 (Version 1.0)
  17.  *        September 7th, 1984 (Version 1.1)
  18.  *        November 7th, 1985 (Version 1.2)
  19.  *
  20.  * Version Modifications:
  21.  *
  22.  * 24/09/84 - "read()" and "write()" accept maximum of 32767
  23.  *          bytes, not 32768. Functions "copy_file()" and
  24.  *          "verify_file()" modified accordingly.
  25.  * 07/11/85 - function "copy_file()" incorrectly copied file
  26.  *          when last invocation of "read()" exactly filled
  27.  *          input file buffer or input file was of zero length.
  28.  *          Statement added to break copy loop on "read()"
  29.  *          returning zero.
  30.  *
  31.  * BU utilizes the undocumented "archive" file attribute feature
  32.  * of CP/M-80 Versions 2.x and CP/M-86 to automatically detect
  33.  * files that have been changed since the disk was last "backed
  34.  * up" and copy them (with verification) to a backup disk. This
  35.  * program performs the same action as the "A" option of PIP 
  36.  * under Digital Research's MP/M 2, for which the Archive
  37.  * attribute is documented.
  38.  *
  39.  * Usage: BU x[:afn] y [-AFHQSn]
  40.  *
  41.  *      where x = drive name of disk to be backed up
  42.  *        y = drive name of backup disk
  43.  *
  44.  *      and the optional arguments are:
  45.  *
  46.  *        -A      All files, regardless of status
  47.  *        -F      Fast copy (without verification)
  48.  *        -H      Hard disk (files may be split)
  49.  *        -Q        Query each file before backup
  50.  *        -S      System attribute copied to backup
  51.  *        -n      Backup USER 'n' files only (0-31)
  52.  *        afn      Any legal CP/M ambiguous fileref
  53.  *              (can only be used with -n option)
  54.  */
  55.  
  56. #include "stdio.h"
  57. #include "ctype.h"    /* Contains macro for "isdigit()" */
  58.  
  59. /*** DEFINITIONS ***/
  60.  
  61. #define AZTEC    1    /* Aztec CII v1.06b (CP/M-80) */
  62. #define DESMET    0    /* DeSmet C88 v2.2 (CP/M-86) */
  63.  
  64. #if DESMET
  65. #define movmem(src,dest,len)    _mov(len,src,dest)
  66. #endif
  67.  
  68. #define ERROR       -1
  69. #define DEL       -1    /* Deleted fileref flag */
  70. #define ALL       -1    /* All user numbers flag */
  71. #define TRUE       -1
  72. #define FALSE        0
  73. #define SUCCESS     0
  74. #define O_RDONLY    0
  75. #define USER_ERR    0    /* Specified fileref must only be used
  76.                with -number command-line option */
  77. #define BAD_FREF    1    /* Illegal file reference */
  78. #define BAD_ARGS    2    /* Illegal command line */
  79. #define BAD_OPT     3    /* Illegal option */
  80. #define BAD_USER    4    /* Illegal user number */
  81. #define BAD_DRV     5    /* Illegal drive names */
  82. #define SAME_DRV    6    /* Same drive name for output as input */
  83. #define OPN_ERR     8    /* File open error */
  84. #define READ_ERR    9    /* File read error */
  85. #define CLS_ERR    10    /* File close error */
  86. #define BAD_VFY    11    /* File verify error */
  87. #define DIR_IO        6    /* BDOS Direct I/O service */
  88. #define RESET_DRV  13    /* BDOS Reset All Drives service */
  89. #define SEL_DRV       14    /* BDOS Select Drive service */
  90. #define SRCH_F       17    /* BDOS Search Next service */
  91. #define SRCH_N       18    /* BDOS Search Next service */
  92. #define GET_DRV       25    /* BDOS Get Default Drive service */
  93. #define SET_DMA       26    /* BDOS Set DMA Address service */
  94. #define SET_ATT       30    /* BDOS Set File Attributes service */
  95. #define USER_CODE  32    /* BDOS Get/Set User Code service */
  96. #define MAX_USER   32    /* 32 user codes under CP/M (see DR's
  97.                documentation for BDOS Service 32) */
  98.  
  99. /*** GLOBAL VARIABLES ***/
  100.  
  101. char ent_drv,    /* Entry drive code */
  102.      ent_user,    /* Entry user code */
  103.      cur_user;    /* Current user code */
  104.  
  105. /*** MAIN BODY OF CODE ***/
  106.  
  107. main(argc,argv)
  108. int argc;
  109. char *argv[];
  110. {
  111.   char in_dsk,        /* Drive name of input disk */
  112.        out_dsk,        /* Drive name of output (backup) disk */
  113.        in_drv,        /* Drive code of input disk */
  114.        out_drv,        /* Drive code of output disk */
  115.        seg_no,        /* Segment number for split files */
  116.        in_file[15],    /* Fileref of current input file */
  117.        out_file[15],    /* Fileref of current output file */
  118.        c,        /* Scratch variable */
  119.        *s,        /* Scratch string pointer */
  120.        *buffer,        /* Pointer to directory entry returned */
  121.             /* by "srch_file()" */
  122.        *srch_file(),
  123.        *malloc();
  124.  
  125.   /* File control blocks of current input and output files */
  126.  
  127.   static char in_fcb[33],    /* (Automatically initialized */
  128.           out_fcb[33];    /* to zero by compiler) */
  129.  
  130.   /* Structure for linked list of filerefs */
  131.  
  132.   struct file_ref
  133.   {
  134.     char name[12];        /* File reference */
  135.     struct file_ref *next;    /* Pointer to next instance */
  136.   } root,            /* Start of linked list */
  137.     *fref_1,            /* Scratch pointers to */
  138.     *fref_2;            /* linked list instances */
  139.  
  140.   /* Initialized file control block for "srch_file()". This FCB
  141.      is for a fully ambiguous fileref that causes "srch_file()"
  142.      to return all directory entries for the current default
  143.      drive. */
  144.  
  145.   static char fcb[] = {'?','?','?','?','?','?','?','?',
  146.                '?','?','?','?','?',0,0,0};
  147.  
  148.   int file_cnt = 0,    /* Count of file to be backed up */
  149.       dup_flag,        /* Duplicate fileref flag */
  150.       all_files,    /* All_files flag (cmd-line option) */
  151.       fast_copy,    /* Fast copy flag (cmd-line option) */
  152.       hard_disk,    /* Hard disk flag (cmd-line option) */
  153.       query,        /* Query flag (cmd-line option) */
  154.       system,        /* System flag (cmd-line option) */
  155.       user_no,        /* User number (cmd-line option) */
  156.       next_flag = FALSE;/* Flag to indicate to "srch_file()"
  157.                that a "search next" is required */
  158.  
  159.   register int i,j;    /* Loop indices */
  160.  
  161.   long begin,        /* Input file position variables */
  162.        end;
  163.  
  164.   /* Display program header */
  165.  
  166.   printf("\nBU Version 1.2");
  167.   printf("                                 Copyright 1985");
  168.   printf(" byHeart Software\n\n");
  169.  
  170.   /* Initialize command-line options */
  171.  
  172.   all_files = FALSE;    /* Copy only non-archived files */
  173.   fast_copy = FALSE;    /* Copy files with verification */
  174.   hard_disk = FALSE;    /* Files will not be split across backup
  175.                disks if remaining capacity of backup
  176.                disk is less than current file size */
  177.   query = FALSE;    /* Backup without query */
  178.   system = FALSE;    /* Assign directory attribute to all
  179.                backup files */
  180.   user_no = ALL;    /* Backup files in all user areas */  
  181.  
  182.   /* Parse command line for user-selected options (if any) */
  183.             
  184.   if(argc < 3)
  185.     error(BAD_ARGS,NULL);    /* Illegal command line */
  186.   if(argc > 3)
  187.   {
  188.     i = 3;  /* Start with third command-line argument */
  189.     while(i < argc)
  190.     {
  191.       if(*argv[i] != '-')
  192.     error(BAD_OPT,argv[i]);  /* Missing leading '-' */
  193.       s = argv[i]+1;
  194.       while(*s)
  195.       {
  196.     if(*s == 'A')        /* Check for all_files option */
  197.       all_files = TRUE;
  198.     else if(*s == 'F')  /* Check for fast copy option */
  199.       fast_copy = TRUE;
  200.     else if(*s == 'H')  /* Check for hard disk option */
  201.       hard_disk = TRUE;
  202.     else if(*s == 'Q')  /* Check for query option */
  203.       query = TRUE;
  204.     else if(*s == 'S')  /* Check for system option */
  205.       system = TRUE;
  206.     else if(isdigit(*s))  /* Check for user number option */
  207.     {
  208.       user_no = *s++ - '0';
  209.       if(isdigit(*s))
  210.         user_no = user_no * 10 + *s++ - '0';
  211.       if(user_no < 0 || user_no > 31)
  212.         error(BAD_USER,argv[i]);
  213.       continue;
  214.     }
  215.     else
  216.       error(BAD_OPT,argv[i]);
  217.     s++;
  218.       }
  219.     i++;
  220.     }
  221.   }
  222.  
  223.   /* Validate input parameters */
  224.  
  225.   if(*(argv[1]+1) != '\0')  /* Check for specified fileref */
  226.   {
  227.     if(user_no == ALL)         /* Can only use with specified */
  228.       error(USER_ERR,NULL);  /* user number (-n option) */
  229.  
  230.     /* Modify "fcb[]" to incorporate fileref */
  231.  
  232.     if(copy_fref(fcb,argv[1]) == ERROR)
  233.       error(BAD_FREF,argv[1]);
  234.   }
  235.   if(*argv[1] < 'A' || *argv[1] > 'P' ||
  236.       *argv[2] < 'A' || *argv[2] > 'P')
  237.     error(BAD_DRV,NULL);    /* Illegal drive names */
  238.   if(*argv[1] == *argv[2])
  239.     error(SAME_DRV,NULL);    /* Drive names are same */
  240.  
  241.   /* Save entry drive and user codes */
  242.  
  243.   ent_drv = bdos(GET_DRV);
  244.   ent_user = bdos(USER_CODE,0xff);
  245.  
  246.   /* Calculate input and output drive codes */
  247.  
  248.   in_drv = (in_dsk = *argv[1]) - 'A' + 1;
  249.   out_drv = (out_dsk = *argv[2]) - 'A' + 1;
  250.  
  251.   /* Set default drive to input drive */
  252.  
  253.   bdos(SEL_DRV,in_drv-1);
  254.  
  255.   /* Set user code to "user_no" if -n option specified */
  256.  
  257.   if(user_no != ALL)
  258.     bdos(USER_CODE,user_no);
  259.  
  260.   /* Read first 12 bytes of updated active directory entries into
  261.      linked list of filerefs. If first byte of entry is 0xe5,
  262.      then file has been erased. */
  263.  
  264.   root.next = NULL;    /* Initialize linked list root */
  265.   fref_1 = &root;    /* Initialize linked list pointer */
  266.   while(buffer = srch_file(fcb,next_flag))
  267.   {
  268.     /* Bit 7 of third filetype byte (t3') in directory entry
  269.        is the Archive attribute indicator. The BDOS sets this
  270.        bit to zero whenever it updates a directory entry. */
  271.  
  272.     if(buffer[0] != 0xe5 &&
  273.       (buffer[0] == user_no || user_no == ALL) &&
  274.       (!(buffer[11] & 0x80) || all_files))
  275.     {
  276.       fref_1->next =    /* Allocate space for fileref instance */
  277.       (struct file_ref *) malloc(sizeof(struct file_ref));
  278.       fref_1 = fref_1->next;    /* Assign space to instance */
  279.       movmem(buffer,fref_1->name,12);  /* Move fileref to */
  280.                        /* linked list instance */
  281.       fref_1->next = NULL;  /* Indicate current end of list */
  282.     }
  283.     next_flag = TRUE;    /* Only first call to "srch_file()" */
  284.             /* should be made with "next_flag" */
  285.   }            /* set to FALSE */
  286.  
  287.   /* If no files have been backed up ... */
  288.  
  289.   if(!root.next)  /* Null "root.next" indicates no files have */
  290.     {          /* been changed */
  291.       printf("NO FILES HAVE BEEN UPDATED");
  292.       if(user_no != ALL)
  293.     printf(" in user area %d\n",user_no);
  294.       else
  295.     putchar('\n');
  296.       reset();    /* Reset user and drive codes to entry values */
  297.       exit(0);
  298.     }
  299.  
  300.   /* There may be duplicate filerefs in linked list due to some
  301.      files occupying more than one extent on the disk. These
  302.      duplicates must be marked as "deleted" in the list.
  303.      (Duplicate filerefs with different user codes are valid.) */
  304.  
  305.   /* For all filerefs ... */
  306.  
  307.   fref_1 = &root;  /* Initialize a linked list pointer */
  308.   while(fref_1->next)
  309.   {
  310.     fref_1 = fref_1->next;  /* Root instance is NULL entry */
  311.     dup_flag = FALSE;        /* Reset duplicate fileref flag */
  312.  
  313.     /* For all preceding filerefs ... */
  314.  
  315.     fref_2 = &root;  /* Initialize another linked list pointer */
  316.     fref_2 = fref_2->next;  /* Skip root instance */
  317.     while(fref_2->next != fref_1->next)
  318.     {
  319.       /* Compare filerefs (ignore deleted filerefs and different
  320.      user codes) */
  321.  
  322.       if(fref_2->name[0] != DEL && fref_1->name[0] == fref_2->name[0])
  323.     if(!strncmp(fref_1->name+1,fref_2->name+1,11))
  324.     {
  325.       dup_flag = TRUE;    /* Indicate duplicate fileref */
  326.       break;
  327.     }
  328.       fref_2 = fref_2->next;
  329.     }
  330.     if(dup_flag == TRUE)
  331.       fref_1->name[0] = DEL;    /* Delete if duplicate fileref */
  332.     else
  333.       file_cnt++;        /* Increment file count */
  334.   }
  335.  
  336.   /* Display file copy header */
  337.  
  338.   printf("Number of files to be copied: %d\n\n",file_cnt);
  339.   printf("User:    Files being copied to Drive %c:\n\n",
  340.      out_dsk);
  341.  
  342.   /* Initialize current input and output fileref templates */
  343.  
  344.   in_file[0] = in_dsk;
  345.   out_file[0] = out_dsk;
  346.   in_file[1] = out_file[1] = ':';
  347.   in_file[10] = out_file[10] = '.';
  348.   in_file[14] = out_file[14] = '\0';
  349.  
  350.   /* Initialize current input and output FCB templates */
  351.  
  352.   in_fcb[0] = in_drv;
  353.   out_fcb[0] = out_drv;
  354.  
  355.   /* For all validated filerefs do ... */
  356.  
  357.   for(cur_user = 0; cur_user < MAX_USER; cur_user++)
  358.   {
  359.     if(user_no != ALL)
  360.       if(cur_user != user_no)
  361.     continue;
  362.     bdos(USER_CODE,cur_user);    /* Set user code to "cur_user" */
  363.     fref_1 = &root;    /* Initialize linked list pointer */
  364.     while(fref_1->next)
  365.     {
  366.       fref_1 = fref_1->next;    /* Root instance is NULL entry */
  367.       if(fref_1->name[0] == cur_user)
  368.       {
  369.     /* Update the current input and output FCB's */
  370.  
  371.     movmem(fref_1->name+1,in_fcb+1,11);
  372.     movmem(fref_1->name+1,out_fcb+1,11);
  373.  
  374.     /* Reset the Read-Only and System attribute bits of the
  375.        FCB's so that the file can be copied and displayed
  376.        (unless the "system" flag is TRUE) */
  377.  
  378.     out_fcb[9] &= 0x7f;    /* Read-Only attribute */
  379.     if(!system)
  380.       out_fcb[10] &= 0x7f;    /* System attribute */
  381.  
  382.     /* Set the Archive attribute bit of the FCB's to indicate
  383.        that the file has been backed up */
  384.  
  385.     in_fcb[11] |= 0x80;
  386.     out_fcb[11] |= 0x80;
  387.  
  388.     /* Move the fileref from the FCB's to the initialized
  389.        input and output fileref templates */
  390.  
  391.     movmem(in_fcb+1,in_file+2,8);    /* Filename move */
  392.     movmem(out_fcb+1,out_file+2,8);
  393.     movmem(in_fcb+9,in_file+11,3);    /* Filetype move */
  394.     movmem(out_fcb+9,out_file+11,3);
  395.  
  396.     /* Strip high order bits off filerefs to form proper
  397.        ASCII characters */
  398.  
  399.     for(j = 2; j <= 13; j++)
  400.     {
  401.       in_file[j] &= 0x7f;
  402.       out_file[j] &= 0x7f;
  403.     }
  404.  
  405.     /* Display the filerefs */
  406.  
  407.     printf(" %2d      %s --> %s",cur_user,in_file,out_file);
  408.  
  409.     /* Query operator for backup if indicated by "query"
  410.        flag */
  411.  
  412.     if(query)
  413.     {
  414.       printf("  O.K. to backup?  ");
  415.       if((c = in_chr()) == 'y' || c == 'Y')
  416.         puts("Yes");
  417.       else
  418.       {
  419.         puts("No");
  420.         continue;    /* Go do next fileref if "No" */
  421.       }
  422.     }
  423.     else
  424.       putchar('\n');
  425.  
  426.     /* Copy file from the input disk to the output disk */
  427.  
  428.     if(hard_disk)    /* Split file across backup disks if */
  429.     {        /* necessary */
  430.       begin = 0L;    /* Initialize file position pointer */
  431.       seg_no = 0;    /* and split file segment number */
  432.       do
  433.       {
  434.         /* Reset the Read-Only attribute of the output file
  435.            (if it exists) so that the input file can be
  436.            copied to it */
  437.  
  438.         bdos(SET_ATT,out_fcb);
  439.  
  440.         end = copy_file(in_file,out_file,begin);
  441.         if(!fast_copy)  /* Verify file unless -F selected */
  442.           verify_file(in_file,out_file,begin);
  443.  
  444.         /* Set the Read-Only attribute of the output file */
  445.  
  446.         out_fcb[9] |= 0x80;
  447.         bdos(SET_ATT,out_fcb);
  448.         out_fcb[9] &= 0x7f;    /* ... and reset the fcb */
  449.         if(end != NULL)
  450.         {
  451.           /* File has been partially written on current
  452.          backup disk - new disk required to continue */
  453.  
  454.           new_disk(out_file,hard_disk);
  455.  
  456.           /* Append segment number to filename of output
  457.          fileref (e.g. - B:FILE.TYP will become
  458.          B:FILE--01.TYP) */
  459.  
  460.           seg_no++;
  461.           for(j = 2; j <= 7; j++)    /* Change spaces to */
  462.         if(out_file[j] == ' ')    /* '-' character */
  463.           out_file[j] = '-';
  464.           out_file[8] = seg_no/10 + '0';  /* Append segment */
  465.           out_file[9] = seg_no%10 + '0';  /* number */
  466.  
  467.           /* Display filerefs again */
  468.  
  469.           printf(" %2d      %s --> %s\n",
  470.           cur_user,in_file,out_file);
  471.         }
  472.         begin = end;
  473.       }
  474.       while(end != NULL);  /* Loop until file is written */
  475.     }
  476.     else
  477.     {
  478.       /* Reset the Read-Only attribute of the output file
  479.          (if it exists) */
  480.  
  481.       bdos(SET_ATT,out_fcb);
  482.  
  483.       if(copy_file(in_file,out_file,0L) != NULL)
  484.       {
  485.         /* Disk was full - erase partially written file, back
  486.            up fileref pointer and rewrite file to new disk */
  487.  
  488.         unlink(out_file);
  489.         new_disk(out_file,hard_disk);
  490.         i--;
  491.         continue;
  492.       }
  493.       if(!fast_copy)  /* Verify file unless -F selected */
  494.         verify_file(in_file,out_file,0L);
  495.  
  496.       /* Set the Read-Only attribute of the output file */
  497.  
  498.       out_fcb[9] |= 0x80;
  499.       bdos(SET_ATT,out_fcb);
  500.     }
  501.  
  502.     /* Set the Archive attribute of the input file to
  503.        indicate that the file was successfully backed up */
  504.  
  505.     bdos(SET_ATT,in_fcb);
  506.       }
  507.     }
  508.   }
  509.   reset();    /* Reset user and drive codes to entry values */
  510. }
  511.  
  512. /*** FUNCTIONS ***/
  513.  
  514. /* Search for first or next directory entry */
  515.  
  516. char *srch_file(fcb_ptr,next_flag)
  517. char *fcb_ptr;        /* Pointer to file control block */
  518. int next_flag;        /* Flag to indicate "search next" */
  519. {
  520.   static char sf_cur[32],  /* Current directory entry buffer */
  521.               sf_fcb[36];  /* File control block buffer */
  522.  
  523.   int index,  /* Index of directory entry in DMA buffer */
  524.       *ptr;   /* Pointer to directory entry in DMA buffer */
  525.  
  526.   bdos(SET_DMA,0x80);    /* Set DMA address to 80h */
  527.   if(!next_flag)
  528.   {
  529.     movmem(fcb_ptr,sf_fcb,16);    /* Initialize FCB buffer */
  530.     if((index = bdos(SRCH_F,sf_fcb)) == 0xff)  /* Find first */
  531.       return NULL;    /* Return NULL if unsuccessful */
  532.   }
  533.   else
  534.     if((index = bdos(SRCH_N,NULL)) == 0xff)  /* Find next */
  535.       return NULL;    /* Return NULL if unsuccessful */
  536.  
  537.   /* BDOS services 17 and 18 leave four consecutive directory
  538.      entries of 32 bytes each in the 128-byte DMA buffer and also
  539.      returns an index value of 0, 1, 2 or 3 to indicate the
  540.      correct directory entry in the accumulator. The "bdos()"
  541.      function returns this index value. */
  542.  
  543.   ptr = 0x80 + index * 32;  /* Calculate pointer to directory
  544.                    entry */
  545.   movmem(ptr,sf_cur,32);  /* Move directory entry to current
  546.                  directory entry buffer */
  547.   return sf_cur;
  548. }
  549.  
  550. /* Copy file starting at "offset" from beginning */
  551.  
  552. copy_file(in_file,out_file,offset)
  553. char *in_file,        /* Input fileref */
  554.      *out_file;        /* Output fileref */
  555. long offset;        /* Input file position offset */
  556. {
  557.   register int in_cnt,    /* Character counts for unbuffered I/O */
  558.            out_cnt;
  559.   int fd_in,        /* Input file descriptor */
  560.       fd_out,        /* Output file descriptor */
  561.       full_disk = FALSE;  /* Full disk flag */
  562.  
  563.   char *buffer,       /* Input file buffer */
  564.        *buff_ptr,  /* Pointer to current position in "buffer[]" */
  565.        *malloc();
  566.  
  567.   unsigned buf_size = 32640;  /* Initial memory allocation size */
  568.  
  569.   /* "read()" accepts a maximum of 32767 bytes at a time. Allocate
  570.      as much memory as possible up to this limit for the input
  571.      buffer, using 128 byte decrements. */
  572.  
  573.   do
  574.     if(buffer = malloc(buf_size))
  575.       break;
  576.   while(buf_size -= 128);
  577.  
  578.   /* Open input file for unbuffered Read-Only access */
  579.  
  580.   if((fd_in = open(in_file,O_RDONLY)) == ERROR)
  581.     error(OPN_ERR,in_file);
  582.  
  583.   /* Create the output file by first deleting it (if it
  584.      exists), then opening it for unbuffered Write-Only
  585.      access. */
  586.  
  587.   if((fd_out = creat(out_file,NULL)) == ERROR)
  588.     error(OPN_ERR,out_file);
  589.  
  590.   /* Initialize input file position pointer to "offset" */
  591.  
  592.   lseek(fd_in,offset,0);
  593.  
  594.   /* Copy input file to output file by buffering data
  595.      through "buffer[]" */
  596.  
  597.   do
  598.   {
  599.     if((in_cnt = read(fd_in,buffer,buf_size)) == ERROR)
  600.       error(READ_ERR,in_file);
  601.     if(in_cnt == 0)    /* End of file */
  602.       break;
  603.     buff_ptr = buffer;  /* Initialize "buffer[]" pointer */
  604.     out_cnt = 0;    /* and "out_cnt" */
  605.     do
  606.     {
  607.       /* Write contents of "buffer[]" to output file in 128
  608.      byte records until either the buffer is written or a
  609.      write error occurs. Since the 0x1a (^Z) character CP/M
  610.      uses as an EOF marker is a valid file character for non-
  611.      ASCII files, "read()" always reads the last 128 byte
  612.      record of a file under CP/M. */
  613.  
  614.       if(write(fd_out,buff_ptr,128) != 128)
  615.       {
  616.     /* The standard implementation of "write()" does not
  617.        distinguish between a full disk or directory and a
  618.        write error in its returned error code. Thus, it is
  619.        assumed that an error means a full disk/directory. */
  620.  
  621.     full_disk = TRUE;
  622.     break;
  623.       }
  624.       buff_ptr += 128;  /* Increment "buffer[]" ptr */
  625.       out_cnt += 128;  /* Update count of chars written */
  626.     }
  627.     while(in_cnt > out_cnt);    /* Until end of "buffer[]" */
  628.     offset += out_cnt;    /* Update input file position pointer */
  629.     if(full_disk == TRUE)
  630.       break;
  631.   }
  632.   while(in_cnt == buf_size);    /* Until end of file */
  633.   free(buffer);        /* Deallocate buffer space */
  634.   if(close(fd_in) == ERROR)    /* Close the files */
  635.     error(CLS_ERR,in_file);
  636.   if(close(fd_out) == ERROR)
  637.     error(CLS_ERR,out_file);
  638.  
  639.   /* If full disk return new offset for input file, else */
  640.   /* return NULL to indicate completion of file copy operation */
  641.  
  642.   return (full_disk ? offset : NULL);
  643. }
  644.  
  645. /* Compare portion of input file starting at "offset" from begin-
  646.    ning of file with output file */
  647.  
  648. verify_file(in_file,out_file,offset)
  649. char *in_file,        /* Input fileref */
  650.      *out_file;        /* Output fileref */
  651. long offset;        /* Input file position offset */
  652. {
  653.   register int match_cnt;    /* Scratch variable */
  654.  
  655.   int out_cnt,    /* Character counts for unbuffered I/O */
  656.       fd_in,    /* Input file descriptor */
  657.       fd_out;    /* Output file descriptor */
  658.  
  659.   char *buffer,        /* Dynamically-allocated buffer */
  660.        *in_ptr,        /* Input file buffer pointer */
  661.        *out_ptr,    /* Output file buffer pointer */
  662.        *malloc();
  663.  
  664.   unsigned buf_size = 65280;  /* Initial memory allocation size */
  665.  
  666.   /* "read()" and "write()" accept a maximum of 32767 bytes at a
  667.      time. Allocate as much memory as possible up to this limit
  668.      for both the input and output buffers, using 256 byte
  669.      decrements (128 bytes for each buffer). */
  670.  
  671.   do
  672.     if(buffer = malloc(buf_size))
  673.       break;
  674.   while(buf_size -= 256);
  675.  
  676.   /* Divide "buffer[]" in two for "in_ptr" and "out_ptr" */
  677.  
  678.   buf_size /= 2;
  679.  
  680.   if((fd_in = open(in_file,O_RDONLY)) == ERROR)  /* Open files */
  681.     error(OPN_ERR,in_file);
  682.   if((fd_out = open(out_file,O_RDONLY)) == ERROR)
  683.     error(OPN_ERR,out_file);
  684.   lseek(fd_in,offset,0);  /* Initialize file position pointer */
  685.  
  686.   /* Read in characters from both files and compare */
  687.  
  688.   do
  689.   {
  690.     in_ptr = buffer;    /* Assign buffer pointers */
  691.     out_ptr = in_ptr + buf_size;
  692.     if(read(fd_in,in_ptr,buf_size) == ERROR)
  693.       error(READ_ERR,in_file);
  694.     if((out_cnt = read(fd_out,out_ptr,buf_size)) == ERROR)
  695.       error(READ_ERR,out_file);
  696.     match_cnt = out_cnt;
  697.     while(match_cnt--)            /* Verify character */
  698.       if(*in_ptr++ != *out_ptr++)    /* by character, and */
  699.       {                    /* delete the output */
  700.     if(close(fd_out) == ERROR)    /* file if they fail */
  701.       error(CLS_ERR,out_file);    /* to match */
  702.     unlink(out_file);
  703.     error(BAD_VFY,out_file);
  704.       }
  705.   }
  706.   while(out_cnt == buf_size);    /* Until end of output file */
  707.   free(buffer);            /* Deallocate buffer space */
  708.   if(close(fd_in) == ERROR)    /* Close the files - verifi- */
  709.     error(CLS_ERR,in_file);    /* cation was successful */
  710.   if(close(fd_out) == ERROR)
  711.     error(CLS_ERR,out_file);
  712. }
  713.  
  714. /* Copy fileref to file control block */
  715.  
  716. copy_fref(fcb,fref)
  717. char *fcb,        /* Pointer to file control block */
  718.      *fref;        /* Pointer to fileref */
  719. {
  720.   char c;    /* Scratch variable */
  721.   int i,    /* Fileref index variable */
  722.       j,    /* FCB index variable */
  723.       k,    /* Scratch variable */
  724.       done;    /* Loop break flag */
  725.  
  726.   if(fref[1] != ':' || fref[2] == '\0')
  727.     return ERROR;  /* No drive code separator or null fileref */
  728.  
  729.   /* Calculate drive code from drive name and put in FCB */
  730.  
  731.   fcb[0] = fref[0] - 'A' + 1;
  732.  
  733.   /* Process remainder of fileref */
  734.  
  735.   done = FALSE;
  736.   for(i = 2,j = 1;i <= 9;i++,j++)    /* Skip drive code in */
  737.   {                    /* fileref */
  738.     switch(c = fref[i])
  739.     {
  740.       case '.':        /* Filetype separator */
  741.     if(i == 2)
  742.       return ERROR;    /* Null filename */
  743.     for( ; j <= 8; j++)    
  744.       fcb[j] = ' ';    /* Pad filename with trailing blanks */
  745.     done = TRUE;
  746.     break;
  747.       case '*':        /* Match any following string */
  748.     for( ; j <= 8; j++)
  749.       fcb[j] = '?';    /* Pad filename with trailing */
  750.     i++;        /* question marks */
  751.     done = TRUE;
  752.     break;
  753.       case '\0':    /* End of fileref */
  754.     for( ; j <= 11; j++)  /* Pad FCB with trailing spaces */
  755.       fcb[j] = ' ';
  756.     return SUCCESS;
  757.       case ',':        /* Illegal filename characters */
  758.       case ';':
  759.       case ':':
  760.       case '=':
  761.       case '[':
  762.       case ']':
  763.       case '_':
  764.       case '<':
  765.       case '>':
  766.     return ERROR;
  767.       default:
  768.     if(c >= '!' && c <= '~')
  769.       fcb[j] = c;    /* Copy character from fileref to FCB */
  770.     else
  771.       return ERROR;    /* Nonprintable character or ' ' */
  772.     }
  773.     if(done)
  774.       break;
  775.   }
  776.   c = fref[i];
  777.   if((c = fref[i]) == '\0')    /* End of fileref */
  778.   {
  779.     for( ; j <= 11; j++)  /* Pad FCB with trailing spaces */
  780.       fcb[j] = ' ';
  781.     return SUCCESS;
  782.   }
  783.   else if(c == '.')    /* Filetype separator */
  784.   {
  785.     i++;
  786.     k = i + 2;        /* Set limit of 3 characters */
  787.     for( ; i <= k; i++,j++)
  788.     {
  789.       done = FALSE;
  790.       switch(c = fref[i])
  791.       {
  792.     case '*':    /* Match any following string */
  793.       for( ; j <= 11; j++)
  794.         fcb[j] = '?';    /* Pad filetype with trailing */
  795.       return SUCCESS;    /* question marks */
  796.     case '\0':    /* End of fileref */
  797.       for( ; j <= 11; j++)  /* Pad FCB with trailing */
  798.         fcb[j] = ' ';      /* spaces */
  799.       return SUCCESS;
  800.     case '.':        /* Illegal filetype characters */
  801.     case ';':
  802.     case ',':
  803.     case ':':
  804.     case '=':
  805.     case '[':
  806.     case ']':
  807.     case '_':
  808.     case '<':
  809.     case '>':
  810.       return ERROR;
  811.     default:
  812.       if(c >= '!' && c <= '~')
  813.         fcb[j] = c;  /* Copy character from fileref */
  814.       else            /* to FCB */
  815.         return ERROR;  /* Nonprintable character or ' ' */
  816.       }
  817.     }
  818.  
  819.     /* Return ERROR if filetype too long */
  820.  
  821.     return (fref[i] == '\0' ? SUCCESS : ERROR);
  822.   }
  823.   else
  824.     return ERROR;  /* Filename too long  */
  825. }
  826.  
  827. /* Error report */
  828.  
  829. error(n,s)
  830. int n;        /* Error code */
  831. char *s;    /* Pointer to optional string */
  832. {
  833.   switch(n)
  834.   {
  835.     case USER_ERR:
  836.       printf("\007** ERROR - No user number specified **\n");
  837.       break;
  838.     case BAD_FREF:
  839.       printf("\007** ERROR - Illegal file reference: %s **\n",s);
  840.       break;
  841.     case BAD_ARGS:
  842.       printf("\007** ERROR - Illegal command line **\n");
  843.       break;
  844.     case BAD_OPT:
  845.       printf("\007** ERROR - Illegal command line option:");
  846.       printf(" %s **\n",s);
  847.       break;
  848.     case BAD_USER:
  849.       printf("\007** ERROR - User number must be inside range");
  850.       printf(" of 0 to 31 **\n");
  851.       break;
  852.     case BAD_DRV:
  853.       printf("\007** ERROR - Drive names must be inside range");
  854.       printf(" of 'A' to 'P' **\n");
  855.       break;
  856.     case SAME_DRV:
  857.       printf("\007** ERROR - Drive names cannot be equal **\n");
  858.       break;
  859.     case OPN_ERR:
  860.       printf("\007\n** ERROR - Cannot open file %s **\n",s);
  861.       reset();
  862.       exit(0);
  863.     case READ_ERR:
  864.       printf("\007\n** ERROR - Read error on file %s **\n",s);
  865.       reset();
  866.       exit(0);
  867.     case CLS_ERR:
  868.       printf("\007\n** ERROR - Cannot close file %s **\n",s);
  869.       reset();
  870.       exit(0);
  871.     case BAD_VFY:
  872.       printf("\007\n** ERROR - Failed verify on file %s **\n",s);
  873.       reset();
  874.       exit(0);
  875.   }
  876.   printf("\nUsage: BU x[:afn] y [-AFHQSn]\n\n");
  877.   printf("       where x = drive name of disk to be backed up\n");
  878.   printf("             y = drive name of backup disk\n\n");
  879.   printf("       and the optional arguments are:\n\n");
  880.   printf("             -A           All files, regardless of");
  881.   printf(" status\n");
  882.   printf("             -F           Fast copy (without ");
  883.   printf(" verification)\n");
  884.   printf("             -H           Hard disk (files may be");
  885.   printf(" split)\n");
  886.   printf("             -Q           Query each file before");
  887.   printf(" backup\n");
  888.   printf("             -S           System attribute copied to");
  889.   printf(" backup\n");
  890.   printf("             -n           Backup USER 'n' files only");
  891.   printf(" (0-31)\n");
  892.   printf("             -afn         Any legal CP/M ambiguous");
  893.   printf(" fileref\n");
  894.   printf("                          (can only be used with -n");
  895.   printf(" option)\n");
  896.   exit(0);
  897. }
  898.  
  899. /* Request a new backup disk to be inserted in the output drive */
  900.  
  901. new_disk(name,hard_disk)
  902. char *name;
  903. int hard_disk;
  904. {
  905.   char d;
  906.  
  907.   printf("\007\n         ** BACKUP DISK FULL **\n\n");
  908.   if(hard_disk)
  909.     printf("WARNING: -H option active - FILE WILL BE SPLIT\n\n");
  910.   printf("Insert new disk into drive %c to continue.\n",name[0]);
  911.   printf("Type 'C' when ready or 'A' to abort ... ");
  912.   while((d = in_chr()) != 'c' && d != 'C' && d != 'a' && d != 'A')
  913.     ;
  914.   if(d == 'a' || d == 'A')
  915.   {
  916.     unlink(name);
  917.     exit(0);
  918.   }
  919.   else
  920.     printf("\n\n");
  921.   bdos(RESET_DRV,NULL);        /* Reset drives */
  922. }
  923.  
  924. /* Reset user and drive codes to entry values */
  925.  
  926. reset()
  927. {
  928.   bdos(USER_CODE,ent_user);
  929.   bdos(SEL_DRV,ent_drv);
  930. }
  931.  
  932. /* Get character from current CP/M CON: device without echo */
  933.  
  934. in_chr()
  935. {
  936.   int c;
  937.  
  938.   do
  939.     c = bdos(DIR_IO,0xff);
  940.   while(!c);
  941.   return c;
  942. }
  943.  
  944. /* Additional function required for DeSmet C version of BU86.C */
  945.  
  946. #if DESMET
  947. bdos(beta,delta)
  948. int beta,
  949.     delta;
  950. {
  951.   return _os(beta,delta);
  952. }
  953. #endif
  954.  
  955. /*** EXPLANATION OF AZTEC CII STDIO.H FUNCTIONS ***/
  956.  
  957. /* bdos(bc,de)    | DeSmet equivalent is defined under FUNCTIONS |
  958.  * int bc,de;
  959.  *
  960.  * Calls CP/M's BDOS with 8080 CPU register pair BC set to "bc"
  961.  * and register pair DE set to "de". The value returned by the
  962.  * 8080 CPU accumulator is the return value.
  963.  *
  964.  * movmem(src,dest,length)  | DeSmet equivalent is defined  |
  965.  * char *dest, *src;        | under DEFINITIONS            |
  966.  * int length;
  967.  *
  968.  * Moves data from "src" to "dest". The number of bytes is
  969.  * specified by the parameter "length".
  970.  *
  971.  * strncmp(str1,str2,max)  | Identical for DeSmet |
  972.  * char *str1,*str2;
  973.  * int max;
  974.  *
  975.  * Compares "str1" to "str2" for at most "max" characters, and
  976.  * returns NULL if strings are equal, -1 if "str1" is less than
  977.  * "str2", and +1 if "str1" is greater than "str2".
  978.  */
  979.  
  980. /* End of BU.C */
  981.  Additional function required for DeSmet C version of BU86.C */
  982.  
  983. #if DESMET
  984. bdos(